import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
import { Scene } from '@babylonjs/core/scene';
import { Utils } from '../tool/utils';
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
import { AnimationFrame } from '../animation/animation-frame';
import { UniversalCamera } from '@babylonjs/core/Cameras/universalCamera';
import '@babylonjs/core/Collisions/collisionCoordinator';


/**
 * 重力漫游参数对象
 * @param gravity 重力向量
 * @param floorMeshKey 地面网格键名（场景中名称含有此键名的网格将作为地面）
 * @param floorMeshes 地面网格集合
 * @param obstacleMeshKey 障碍网格键名（场景中名称含有此键名的网格将作为障碍）
 * @param obstacleMeshes 障碍网格集合
 * @param floorMeshVisible 地面网格是否可见
 * @param obstacleMeshVisible 障碍网格是否可见
 * @param obstacleMeshPickable 障碍网格是否可拾取
 * @param autoUpdate 是否自动更新（将使得在没有障碍时一直掉落）
 */
interface IGravityRoamingOptions {
    gravity?: Vector3,
    floorMeshKey?: string,
    floorMeshes?: AbstractMesh[],
    obstacleMeshKey?: string,
    obstacleMeshes?: AbstractMesh[],
    floorMeshVisible?: boolean,
    floorMeshPickable?: boolean,
    obstacleMeshVisible?: boolean,
    obstacleMeshPickable?: boolean,
    autoUpdate?: boolean,
}


/**
 * 重力漫游
 * @param scene 所在场景
 * @param options 参数对象
 */
class GravityRoaming {

    private _options: IGravityRoamingOptions;
    private _dta = 1e-12;
    private _req = {};

    public readonly scene: Scene;

    constructor(scene: Scene, options?: IGravityRoamingOptions) {
        this.scene = scene;
        scene.collisionsEnabled = true;
        const ops = options || {};
        ops.gravity = ops.gravity || new Vector3(0, -9.8 / 60, 0);
        ops.floorMeshes = ops.floorMeshes || [];
        ops.obstacleMeshes = ops.obstacleMeshes || [];
        this._options = ops;
        this.scene.gravity = new Vector3().copyFrom(ops.gravity);
        this.searchFloorMeshByKey();
        this.searchObstacleMeshByKey();
        this.setMeshes();
        if (this._options.autoUpdate) this.forceUpdateCamera();
    }

    /**
     * 设置是否自动更新
     */
    public set autoUpdate(enable: boolean) {
        const cur = !!this._options.autoUpdate;
        if (cur === enable) return;
        this._options.autoUpdate = enable;
        if (enable) {
            this.forceUpdateCamera();
        } else {
            AnimationFrame.cancel(this._req);
        }
    }

    /**
     * 设置重力向量
     */
    public set gravity(value: Vector3) {
        this._options.gravity!.copyFrom(value);
        this.scene.gravity.copyFrom(this._options.gravity!);
    }

    /**
     * 获取重力向量
     */
    public get gravity(): Vector3 {
        return this._options.gravity!;
    }

    /**
     * 更新网格
     * @param rebuild 是否重建 
     */
    public updateMeshes(rebuild?: boolean): void {
        if (rebuild) {
            this.searchFloorMeshByKey();
            this.searchObstacleMeshByKey();
        }
        this.setMeshes();
    }

    /**
     * 强制更新相机
     */
    private forceUpdateCamera(): void {
        AnimationFrame.request(() => {
            const cam = this.scene.activeCamera as UniversalCamera;
            if (!cam) return;
            cam.cameraDirection.y += this._dta;

        }, 16, 200, this._req);
    }

    /**
     * 按键名获取地面网格
     */
    private searchFloorMeshByKey(): void {
        const ops = this._options;
        if (ops.floorMeshes) ops.floorMeshes.length = 0;
        if (ops.floorMeshKey) {
            const reg = new RegExp(ops.floorMeshKey, 'i');
            this.scene.meshes.forEach(m => {
                if (reg.test(m.name) || reg.test(m.id)) ops.floorMeshes!.push(m);
            });
        }
        Utils.noDuplication(ops.floorMeshes!, (a, b) => { return a.uniqueId === b.uniqueId });
    }

    /**
     * 按键名获取障碍网格
     */
    private searchObstacleMeshByKey(): void {
        const ops = this._options;
        if (ops.obstacleMeshes) ops.obstacleMeshes.length = 0;
        if (ops.obstacleMeshKey) {
            const reg = new RegExp(ops.obstacleMeshKey, 'i');
            this.scene.meshes.forEach(m => {
                if (reg.test(m.name) || reg.test(m.id)) ops.obstacleMeshes!.push(m);
            });
        }
        Utils.noDuplication(ops.obstacleMeshes!, (a, b) => { return a.uniqueId === b.uniqueId });
    }

    /**
     * 设置网格的相关属性
     */
    private setMeshes(): void {
        const ops = this._options;
        for (const m of ops.floorMeshes!) {
            m.isVisible = !!ops.floorMeshVisible;
            m.isPickable = !!ops.floorMeshPickable;
            m.checkCollisions = true;
        }
        for (const m of ops.obstacleMeshes!) {
            m.isVisible = !!ops.obstacleMeshVisible;
            m.isPickable = !!ops.obstacleMeshPickable;
            m.checkCollisions = true;
        }
    }

}


export { GravityRoaming };
export type { IGravityRoamingOptions };